<template>
  <div
    class="w-drawer-wrap"
    v-if="showWrapper || pushContent"
    :class="wrapperClasses"
  >
    <div class="w-drawer-wrap__track" v-if="pushContent" :style="trackStyles">
      <div class="w-drawer-wrap__pushable">
        <w-overlay
          v-if="!noOverlay"
          v-model="showDrawer"
          @click="onOutsideClick"
          :persistent="persistent"
          persistent-no-animation="persistent-no-animation"
          :bg-color="overlayColor"
          :opacity="overlayOpacity"
        ></w-overlay>
        <slot name="pushable"></slot>
      </div>
      <transition name="fade">
        <div
          class="w-drawer"
          v-if="!unmountDrawer"
          :class="drawerClasses"
          :style="styles"
        >
          <slot></slot>
        </div>
      </transition>
    </div>
    <template v-else>
      <w-overlay
        v-if="!noOverlay"
        v-model="showDrawer"
        @click="onOutsideClick"
        :persistent="persistent"
        persistent-no-animation="persistent-no-animation"
        :bg-color="overlayColor"
        :opacity="overlayOpacity"
      ></w-overlay>
      <transition :name="transitionName" appear="appear" @after-leave="close">
        <component
          class="w-drawer"
          v-if="showDrawer"
          :is="tag || 'aside'"
          :class="drawerClasses"
          :style="styles"
        >
          <slot></slot>
        </component>
      </transition>
    </template>
  </div>
</template>

<script>
// The complexity in this component is on close:
// we must keep the wrapper in the DOM until the drawer transition is finished.
// Then emit the modelValue update that will trigger the removal of the wrapper from the DOM.
import { ref, computed, watch } from "vue";
const oppositeSides = {
  left: "right",
  right: "left",
  top: "down",
  bottom: "up"
};

export default {
  name: "w-drawer",

  props: {
    modelValue: { default: true },
    left: { type: Boolean },
    right: { type: Boolean },
    top: { type: Boolean },
    bottom: { type: Boolean },
    persistent: { type: Boolean },
    persistentNoAnimation: { type: Boolean },
    fitContent: { type: Boolean },
    width: { type: [Number, String, Boolean] },
    height: { type: [Number, String, Boolean] },
    zIndex: { type: [Number, String, Boolean] },
    color: { type: String },
    bgColor: { type: String },
    noOverlay: { type: Boolean },
    pushContent: { type: Boolean },
    absolute: { type: Boolean },
    overlayColor: { type: String },
    overlayOpacity: { type: [Number, String, Boolean] },
    tag: { type: String, default: "aside" }
  },

  emits: ["input", "update:modelValue", "close"],
  setup(props, ctx) {
    const showWrapper = ref(props.modelValue);
    const showDrawer = ref(props.modelValue);
    const persistentAnimate = ref(false);

    const size = computed(() => {
      let size = props.width || props.height;
      // If a number is passed without units, append `px`.
      if (size && parseInt(size) == size) size += "px";
      return size || false;
    });
    // Return `width` or `height`, `width` by default (position right by default).
    const sizeProperty = computed(() => {
      return (
        (["left", "right"].includes(position.value) && "width") || "height"
      );
    });
    const position = computed(() => {
      return (
        (props.left && "left") ||
        (props.right && "right") ||
        (props.top && "top") ||
        (props.bottom && "bottom") ||
        "right"
      );
    });
    const wrapperClasses = computed(() => {
      return {
        "w-drawer-wrap--fixed": !props.absolute && !props.pushContent,
        "w-drawer-wrap--absolute": props.absolute,
        "w-drawer-wrap--push-content": props.pushContent
      };
    });
    const drawerClasses = computed(() => {
      return {
        [props.color]: props.color,
        [`${props.bgColor}--bg`]: props.bgColor,
        "w-drawer--open": !!showDrawer.value,
        [`w-drawer--${position.value}`]: true,
        "w-drawer--fit-content": props.fitContent,
        "w-drawer--persistent": props.persistent,
        "w-drawer--persistent-animate":
          props.persistent && persistentAnimate.value
      };
    });
    // The track is a wrapper around the pushable content and drawer.
    // It moves inside the overflow hidden outer wrap.
    const trackStyles = computed(() => {
      return (
        props.pushContent &&
        showDrawer.value && {
          transform: `translateX(${position.value === "left" ? "" : "-"}${
            size.value
          })`
        }
      );
    });
    const styles = computed(() => {
      return {
        [`max-${sizeProperty.value}`]: size.value || null,
        zIndex: props.zIndex || props.zIndex === 0 || null
      };
    });
    // In case of pushing content, the showWrapper variable doesn't reflect the behavior:
    // unmount the drawer (remove from DOM) is what it does when showWrapper is false.
    const unmountDrawer = computed(() => {
      return !showWrapper.value;
    });
    const transitionName = computed(() => {
      return `slide-${oppositeSides[position.value]}`;
    });

    function close() {
      showWrapper.value = false;
      ctx.emit("update:modelValue", false);
      ctx.emit("input", false);
      ctx.emit("close", false);
    }
    function onOutsideClick() {
      if (!props.persistent) {
        // The close method is called on animation end, except with pushContent
        // (not using the same transition).
        showDrawer.value = false;
        if (props.pushContent) {
          close();
        }
      } else if (!props.persistentNoAnimation) {
        persistentAnimate.value = true;
        setTimeout(() => (persistentAnimate.value = false), 200); // Must match CSS animation duration.
      }
    }
    watch(
      () => props.modelValue,
      value => {
        if (value) {
          showWrapper.value = true;
        }
        showDrawer.value = value;
      }
    );
    return {
      showWrapper,
      showDrawer,
      persistentAnimate,
      // computed
      size,
      sizeProperty,
      position,
      wrapperClasses,
      drawerClasses,
      trackStyles,
      styles,
      unmountDrawer,
      transitionName,
      // methods
      close,
      onOutsideClick
    };
  }
};
</script>